package proxy;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Scanner;
import java.util.Set;

import message.response.MessageResponse;
import model.FileServerData;
import model.Subscription;
import util.Config;
import client.IRMICallback;

/**
 * 
 * @author matthiaskaran
 *
 */
public class ProxyManagementComponent extends Thread implements IRemoteObject{
	
	private String bindingName;
	private String proxyHost;
	private int proxyRMIPort;
	private String keysDir;
	
	private Proxy proxy;
	private IRemoteObject remote;
	private Registry registry;
	
	private Map<String, Integer> downloadStatistics;
	private List<Subscription> subscriptions;
	
	
	public ProxyManagementComponent(Proxy proxy, Config config) throws MissingResourceException{
		
		this.proxy = proxy;
		
		bindingName = config.getString("binding.name");
		proxyHost = config.getString("proxy.host");
		proxyRMIPort = config.getInt("proxy.rmi.port");
		keysDir = config.getString("keys.dir");
		
		this.downloadStatistics = Collections.synchronizedMap(new HashMap<String, Integer>());
		subscriptions = Collections.synchronizedList(new ArrayList<Subscription>());
		

	}
	
	public synchronized void updateDownloadStatistics(String filename) {
		if (this.downloadStatistics.containsKey(filename)) {
			downloadStatistics.put(filename, downloadStatistics.get(filename) + 1);
			checkSubscriptions();
		}
		else {
			downloadStatistics.put(filename, 1);
		}
	}
	
	private synchronized void checkSubscriptions() {
		for ( Iterator<Subscription> iterator = this.subscriptions.iterator(); iterator.hasNext();) {
			Subscription subscription = iterator.next();
			if (this.downloadStatistics.containsKey(subscription.getFilename())) {
				int numberOfDownloads = this.downloadStatistics.get(subscription.getFilename());
				if (numberOfDownloads >= subscription.getNumberOfDownloads()) {
					try {
						// Variante 1: Clients werden einmal benachrichtigt wenn Downloadanzhal erreicht wurde, die Subscription wird dann geloescht
						
						subscription.getClient().notifySubscription(subscription.getFilename(), numberOfDownloads);
						//downloadStatistics.put(subscription.getFilename(), 0);
						iterator.remove();
						
						// Variante 2: jedesmal wenn Downloadanzahl erreicht wurde, zuruecksetzen (mulitple Clientsubscriptions funktionieren dann nicht mehr)
						/*
						subscription.getClient().notifySubscription(subscription.getFilename(), numberOfDownloads);
						downloadStatistics.put(subscription.getFilename(), 0);
						//iterator.remove();
						*/
					
						
					} catch (RemoteException e) {
						System.out.println("client could not be notified");
					}
				}
				
			}
		}
		
	}

	private void initRMI() {

		try {	
			registry = LocateRegistry.createRegistry(this.proxyRMIPort);
			registry.bind(this.bindingName, (IRemoteObject) this);
			UnicastRemoteObject.exportObject((IRemoteObject) this, 0);

		} catch (RemoteException e) {
			System.out.println("RMI Error");
		} catch (AlreadyBoundException e) {
			System.out.println("RMI Error");
		}

	}

	public void run() {
		initRMI();
	}
	
	public void shutdown() {
		try {
			UnicastRemoteObject.unexportObject((IRemoteObject) this, true);
			UnicastRemoteObject.unexportObject(registry,true);  
		} catch (NoSuchObjectException e) {
			System.out.println("RMI Error");
		}
	}

	@Override
	public int readQuorum() throws RemoteException {
		return proxy.getReadQuorumCount();
	}

	@Override
	public int writeQuorum() throws RemoteException {
		return proxy.getWriteQuorumCount();
	}

	@Override
	public MessageResponse topThreeDownloads() throws RemoteException {
		return new MessageResponse(sortByComparator(this.downloadStatistics));
	}


	@Override
	public MessageResponse rmitest() throws RemoteException {
		return new MessageResponse("RMI method successfully called");
	}
	
	@Override
	public MessageResponse getProxyPublicKey() throws RemoteException {
		File originalKey = new File(this.keysDir, "proxy.pub.pem");
		String fileAsString = "";
		try {
			fileAsString = readFile(originalKey);
		} catch (IOException e) {
			fileAsString = "Error: transmission of public key from Proxy FAILED!";
		}
		return new MessageResponse(fileAsString);
	}
	

	@Override
	public MessageResponse setUserPublicKey(String username, String fileContent) throws RemoteException {
		Date timestamp = new Date();
		File file = new File(this.keysDir, timestamp.toString() + "_"+ username + ".pub.pem");
		try {
			file.createNewFile();

			BufferedWriter out = new BufferedWriter(new FileWriter(file));
			out.write(fileContent);
			out.close();
		} catch (IOException e) {
			return new MessageResponse("Error: transmission of public key from User: " + username + " FAILED!");
		}
		return new MessageResponse("Successfully transmitted public key of user: " +  username);
	}


	@Override
	public MessageResponse subscribe(String filename, int numberOfDownloads, IRMICallback client) throws RemoteException {
		if (fileExists(filename)) {
			this.subscriptions.add(new Subscription(filename, numberOfDownloads, client));
			return new MessageResponse("You subscribed for: " + filename);
		} else {
			return new MessageResponse("File could not be found");
		}
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private String sortByComparator(Map unsortMap) {
		
		if (unsortMap.size() > 0) {
			List list = new LinkedList(unsortMap.entrySet());
	 
			// sort list based on comparator
			Collections.sort(list, new Comparator() {
				public int compare(Object o1, Object o2) {
					return ((Comparable) ((Map.Entry) (o2)).getValue())
	                                       .compareTo(((Map.Entry) (o1)).getValue());
				}
			});
	 
			// create top3 String
			String topThreeString = "";
			int counter=1;
			
			for (Iterator it = list.iterator(); it.hasNext();) {
				if (counter>3) break;
				Map.Entry entry = (Map.Entry) it.next();
				topThreeString += counter + ". " + entry.getKey() + " " + entry.getValue() + "\n";
				counter++;
			}
			
			
			return topThreeString;
		}
		else return "No files downloaded so far";
	}

	public void removeCallbackObjects(String username) {

		for ( Iterator<Subscription> iterator = this.subscriptions.iterator(); iterator.hasNext();) {
			Subscription subscription = iterator.next();
			try {
				if (subscription.getClient().clientName().equals(username)) {
					iterator.remove();

				}
			} catch (RemoteException e) {
				System.out.println("removing CallbackObject from client: "+ username + " failed");
			}
		}
	}
	
	private boolean fileExists(String filename) {
		
		Set<String> allFileNames = new HashSet<String>();
		
		for(FileServerData fs : proxy.getFileServers()) {
			allFileNames.addAll(fs.getFileNames());
		}
		
		return allFileNames.contains(filename);
		
	}
	
	public static String readFile(File file) throws IOException {
	    StringBuilder fileContents = new StringBuilder((int)file.length());
	    Scanner scanner = new Scanner(file);
	    String lineSeparator = System.getProperty("line.separator");

	    try {
	        while(scanner.hasNextLine()) {        
	            fileContents.append(scanner.nextLine() + lineSeparator);
	        }
	        return fileContents.toString();
	    } finally {
	        scanner.close();
	    }
	}





	
	

}
